<?php
/**
 * Description: 提交试卷
 * User: devforma
 * Date: 15/4/9
 * Time: 18:08
 */

require __DIR__ . '/includes/utils.php';
class SubmitPaper implements iController {
    private $userId;

    /**
     * api请求处理主函数
     * @param array $queryArgs 就是$_REQUEST变量
     * @return array api返回结果，必须含response_code
     */
    public function output($queryArgs) {
        global $cmsdb;

        if (validateUser($queryArgs) !== 1 || $queryArgs['paper_type'] == 'vip')
            return genOutput(1000);

        $this->userId = $queryArgs['user_id'];

        if (empty($queryArgs['paper_type']) || !isset($queryArgs['redo']) || !filter_var($queryArgs['paper_id'], FILTER_VALIDATE_INT))
            return genOutput(1000);

        //非试卷类型的练习
        $noPaperArray = array('note', 'auto', 'collect', 'error');
        if (in_array($queryArgs['paper_type'], $noPaperArray, true) || $queryArgs['redo'] == 'true') {
            //非试卷类型的练习(专项、快速、错题、收藏)
            //或者组卷练习(模考，整卷，估分)重新做之前未完成的练习
            //此时paper_id为练习的id
            $exerciseId = $queryArgs['paper_id'];

            $paperExists = $cmsdb->getRow('SELECT `id`, `paper_title`, `paper_id`, `status` FROM `qb_relationship_user_paper` WHERE `id`=:paper_id
            AND `user_id`=:user_id', array(':paper_id' => $queryArgs['paper_id'], 'user_id' => $this->userId));

            if (empty($paperExists)) //没有练习记录，请求非法
                return genOutput(1001);

            if ($paperExists->status == 1) //练习已经完成，重复提交
                return genOutput(3001);

            $exerciseName = $paperExists->paper_title;

            //更新做题记录
            $cmsdb->update('qb_relationship_user_paper', array(
                'duration' => $queryArgs['duration'],
                'question_info' => $queryArgs['questions'],
                'action_time' => date('Y-m-d H:i:s'),
                'status' => $queryArgs['status'] == 'undone' ? 2 : 1,
                'terminal_type' => empty($_GET['terminal_type']) ? '' : trim($_GET['terminal_type'])
            ), array('id' => $queryArgs['paper_id']));

            //重置参数为试卷id
            $queryArgs['paper_id'] = $paperExists->paper_id;

        } else { //组卷练习(模考，整卷)首次的练习

            //模考部分单独处理
            if ($queryArgs['paper_type'] == 'mock') {
                $data = $this->saveMock($queryArgs);
                return $data ? genOutput(1, $data) : genOutput(1000);
            }

            $result = $this->savePaperExercise($queryArgs['paper_id'], $queryArgs['questions'], $queryArgs['duration'],
                $queryArgs['status'], $queryArgs['paper_type']);

            if (empty($result))
                return genOutput(1001);

            $exerciseId = $result[0];
            $exerciseName = $result[1];
        }

        //更新答题记录和用户统计信息
        $defeat = 0.0;
        $score = 0;
        $avgScore = 0.0;
        $changedNotes = array();
        $scores = array(); //行测申论等分数线
        if ($queryArgs['status'] == 'done' && $questions = json_decode($queryArgs['questions'])) {
            $rightCount = 0;
            $totalCount = 0;
            foreach ($questions as $q) {
                $totalCount++;
                if ($q->is_right)
                    $rightCount++;
            }

            $changedNotes = $this->updateUserData($questions, $queryArgs['duration'], $queryArgs['paper_type']);

            $data = array(
                'note_info' => serialize($changedNotes),
                'right_count' => $rightCount,
                'total_count' => $totalCount
            );

            $data['evaluate'] = 0.0;
            if ($queryArgs['paper_type'] === 'mokao') { //mini模考增加分数计算
                $data['evaluate'] = $this->calMiniMokaoScore($rightCount, intval($queryArgs['duration']));
                $defeat = $this->getMiniMokaoRank($queryArgs['paper_id'], $data['evaluate']);

                $score = $data['evaluate'];

            } elseif (in_array($queryArgs['paper_type'], array('evaluate', 'mock', 'entire'), true)) { //整卷类要计算预估分
                $originalScore = $this->getPaperScore($queryArgs['paper_id'], $queryArgs['paper_type'] === 'mock');

                $totalScore = $originalScore['totalForCal'];
                unset($originalScore['totalForCal']);

                $subjectRel = array(
                    'xingce' => '行测',
                    'shenlun' => '申论',
                    'total' => '总分'
                );
                foreach ($originalScore as $key => $sco) {
                    if (empty($sco))
                        continue;

                    $scores[] = array(
                        'name' => $subjectRel[$key],
                        'score' => intval($sco)
                    );
                }

                $data['extra_info'] = serialize($scores);

                $data['evaluate'] = $this->calEntireScore($rightCount, $totalCount, $totalScore);
                $score = $data['evaluate'];


                //整卷类型,计算当时的排名和平均分
                if ($queryArgs['paper_type'] == 'entire') {
                    list($avgScore, $defeat) = $this->getCurrentAvgScore($queryArgs['paper_id'], $data['evaluate']);
                    $data['tmp_rank'] = json_encode(array($avgScore, $defeat));
                }

            }

            $cmsdb->update('qb_relationship_user_paper', $data, array('id' => $exerciseId));

        }

        //mini模考用户答题统计更新缓存
        if ($queryArgs['paper_type'] == 'mokao') {
            $miniMockUsers = getMiniMockUsers($queryArgs['paper_id']);
            $miniMockUsers[$queryArgs['user_id']] = array(
                'id' => $exerciseId,
                'status' => $queryArgs['status'] == 'done' ? 1 : 2,
                'evaluate' => $score
            );

            $cacheKey = 'func:getMiniMockUsers:'.$queryArgs['paper_id'];
            cacheSet($cacheKey, $miniMockUsers, 24*60*60);
        }

        return genOutput(1, array(
            'exercise_name' => $exerciseName,
            'exercise_id' => intval($exerciseId),
            'notes' => $changedNotes,
            'defeat' => $defeat,
            'score' => $score,
            'scores' => $scores,
            'avg_score' => $avgScore
        ));

    }

    /**
     * 获取试卷的平均分和排名
     *
     * @param $paperId
     * @param $score
     * @return array
     */
    private function getCurrentAvgScore($paperId, $score) {
        global $cmsdb;

        $sql = "SELECT `evaluate` FROM `qb_relationship_user_paper` WHERE `paper_id`=:pid AND `paper_type`=4 ORDER BY `id` DESC LIMIT 5000";
        $scores = $cmsdb->getCol($sql, array(':pid' => $paperId));

        if (empty($scores)) {
            return array(round($score, 1), 0.01);
        }

        $higherCount = $total = $count = 0;
        foreach ($scores as $sc) {
            if ($sc > $score) {
                $higherCount++;
            }
            $total += $sc;
            $count++;
        }

        $avg = round($total/$count, 1);
        $rank = round($higherCount/$count, 2);
        return array($avg, $rank);
    }

    /**
     * 检查组卷类型的练习的状态
     * @param $userId
     * @param $paperType
     * @param $paperId
     * @return int
     */
    private function checkPaperStatus($userId, $paperType, $paperId) {
        global $cmsdb;

        $types = array(
            'mokao' => 1,
            'entire' => 4,
            'evaluate' => 7,
            'mock' => 8
        );

        $paperExists = $cmsdb->getRow('SELECT `id`, `status` FROM `qb_relationship_user_paper`
        WHERE `user_id`=:user_id AND `paper_type`='. $types[$paperType] . ' AND `paper_id`=:paper_id',
            array(':user_id' => $userId, ':paper_id' => $paperId));

        if (!empty($paperExists) && $paperExists->status == 1)
            return 1;

        return 0;
    }

    /**
     * 保存有原始试卷(天天模考或整卷练习)的练习记录
     * @param int $paperId 整卷试卷id
     * @param string $questions 答题数据
     * @param int $duration 答题时长
     * @param string $status 完成状态
     * @param string $paperType 试卷类型
     * @return bool
     */
    private function savePaperExercise($paperId, $questions, $duration, $status, $paperType) {
        global $cmsdb;

        $typeId = 0;
        $paperTitle = '';
        if ($paperType == 'mokao') {
            $typeId = 1;
            $paperTitle = getPaperName('mokao', $paperId);

            $exerciseExists = $cmsdb->getVar('SELECT `id` FROM `qb_relationship_user_paper` WHERE `user_id`=:user_id 
            AND `paper_id`=:paper_id AND `paper_type`=1',
                array(':user_id' => $this->userId, ':paper_id' => $paperId));

            if (!empty($exerciseExists)) //已经有记录，请求非法
                return false;

            //试卷参加人数加1
            $cmsdb->query('UPDATE `qb_paper_mokao` SET `persons_num`=`persons_num`+1 WHERE `id`='.intval($paperId));

        } elseif ($paperType == 'entire') {
            $typeId = 4;
            $paperTitle = getPaperName('entire', $paperId);

        } elseif ($paperType == 'mock') {
            $typeId = 8;
            $paperTitle = getPaperName('mock', $paperId);

        } elseif ($paperType == 'evaluate') {
            $typeId = 7;
            $paperTitle = getPaperName('evaluate', $paperId);
        }


        //插入记录
        $now = date('Y-m-d H:i:s');
        $cmsdb->insert('qb_relationship_user_paper', array(
            'user_id' => $this->userId,
            'paper_id' => $paperId,
            'paper_type' => $typeId,
            'paper_title' => $paperTitle,
            'question_info' => $questions,
            'action_time' => $now,
            'create_time' => $now,
            'status' => $status == 'done' ? 1 : 2, //2为未完成
            'duration' => $duration,
            'right_count' => 0,
            'total_count' => 0,
            'terminal_type' => empty($_GET['terminal_type']) ? '' : trim($_GET['terminal_type'])
        ));

        return array($cmsdb->lastInsertId(), $paperTitle);
    }



    /**
     * 更新用户答题统计信息
     * @param array $questions 答题信息
     * @param int $duration 答题时长
     * @param string $paperType 练习类型
     * @return array
     */
    private function updateUserData($questions, $duration, $paperType) {
        global $cmsdb;

        //按知识点统计答题情况
        $changedNotes = array();
        $totalRight = 0;
        $totalCount = 0;
        $statisticsTotalCount = 0;

        $qIds = array();
        foreach ($questions as $q) {
            $qIds[] = $q->id;
        }
        $notes = getNotesByQuestions($qIds);

        foreach ($questions as $q) {
            // 多知识点
            foreach ($notes[$q->id]['id'] as $noteId) {
                if (!isset($changedNotes[$noteId])) {
                    $changedNotes[$noteId] = array(
                        'total' => 0,
                        'right' => 0
                    );
                }

                $changedNotes[$noteId]['total']++;
                if ($q->is_right) $changedNotes[$noteId]['right']++;
            }

            //答案为空时不计入答题总数
            if(!empty($q->answer)) $statisticsTotalCount++;

            $totalCount++;
            if ($q->is_right) $totalRight++;
        }

        $userData = getUserData($this->userId);
        $noteInfo = normalizeUserNoteInfo($userData->note_info);

        //更新该用户的知识点统计信息和能力评估分数
        if ($paperType !== 'mock') {
            $finalChangedNotes = calNoteLevel($noteInfo, $changedNotes, $userData->exam_detail_id); //能力产生变化的知识点
        } else {
            $finalChangedNotes = array();
        }

        /* 2015-10-27 模考类型的练习不进行接下来的处理 */
        if ($paperType === 'mock')
            return $finalChangedNotes;

        //更新usertests表
        $this->saveTest($questions);

        //推荐知识点的更新
        if ($paperType == 'note') {
            $this->updateRecomendNote($noteInfo, $changedNotes);
        }

        $newScore = calEvaluationScore($noteInfo, $userData->exam_detail_id); //预估分

        $today = date('Y-m-d');
        $structureKeys = array('total_time', 'history_score', 'right_count');
        $summary = transformDataStructure(unserialize($userData->summary_info), $structureKeys);

        $summary['right_count'] += $totalRight; //正确的题数
        $summary['total_time'] += $duration; //做题总时长
        $summary['history_score'][$today] = intval($newScore); //历史估分
        if (count($summary['history_score']) > 7) //只记录7天
            array_shift($summary['history_score']);

        $totalCount += $userData->question_count; //总题数
        $accuracy = $summary['right_count']/$totalCount; //总正确率
        $statisticsTotalCount += $userData->question_count;//统计总题数

        //更新排名缓存
        updateScoreRank($userData->exam_detail_id, $userData->score, $newScore);

        //更新统计排名
        updateUserDataRank($userData->exam_detail_id, 'score', $newScore, $userData->score);

        updateUserDataRank($userData->exam_detail_id, 'question_count', $statisticsTotalCount, $userData->question_count);

        updateUserDataRank($userData->exam_detail_id, 'accuracy', $accuracy, $userData->accuracy);

        updateUserDataRank($userData->exam_detail_id, 'y_question_count', $statisticsTotalCount, $userData->question_count,$this->userId);


        $updatedUserData = array(
            'note_info'         => json_encode($noteInfo),
            'score'             => $newScore,
            'summary_info'      => serialize(array_values($summary)),
            'accuracy'          => $accuracy,
            'question_count'    => $statisticsTotalCount
        );

        $cmsdb->update('qb_user_data', $updatedUserData, array('user_id' => $this->userId));


        /**-------------------------------------------------------------
         * 更新该用户的统计数据的缓存
         */
        $userData->note_info = $updatedUserData['note_info'];
        $userData->score = $updatedUserData['score'];
        $userData->summary_info = $updatedUserData['summary_info'];
        $userData->accuracy = $updatedUserData['accuracy'];
        $userData->question_count = $updatedUserData['question_count'];

        $cacheKey = $cacheKey = 'qb_userData:'.$this->userId;
        cacheSet($cacheKey, $userData, 24*60*60);

        unset($updatedUserData);

        return $finalChangedNotes;
    }

    /**
     * 更新推荐的知识点
     *
     * @param $noteInfo
     * @param $changedNotes
     */
    private function updateRecomendNote(&$noteInfo, &$changedNotes) {
        $cNotes = array_keys($changedNotes); //做题做到的知识点

        $noteIdSeq = array_keys($noteInfo); //用户全局的知识点id序列

        $currentPos = 0;
        foreach ($noteInfo as $noteId => &$note) {
            $note = array_values($note); //对note_info数组项进行去字符串key处理
            if ($note[4] == 1 && in_array($noteId, $cNotes)) {

                $note[4] = 0;
                if (isset($noteIdSeq[$currentPos+1])) { //不是最后一个知识点
                    $noteInfo[$noteIdSeq[$currentPos+1]][4] = 1; //推荐知识点向后移
                } else {
                    $noteInfo[$noteIdSeq[0]][4] = 1; //全推荐完,第一个知识点设置为推荐知识点
                }

                break;
            }

            ++$currentPos;
        }
    }


    /**
     * 保存做题数据到test表
     * @param array $data 做题数据数组
     * @return float 本次做题数据的正确率
     */
    private function saveTest($data) {
        global $cmsdb;

        if (is_array($data) && !empty($data)) {
            $sql = 'SELECT ed.exam_id FROM `data_exam_detail` AS ed INNER JOIN `qb_user_data` AS u
            ON ed.id=u.exam_detail_id AND u.user_id=:user_id';
            $testarea_id = $cmsdb->getVar($sql, array(':user_id' => $this->userId));
            //构造insert字符串
            $now = date('Y-m-d H:i:s');
            $values = array();
            $rightCount = 0;
            $totalCount = 0;
            foreach ($data as $q) {
                $values[] = array(
                    'user_id' =>  $this->userId,
                    'question_id' => $q->id,
                    'category_id' => $q->category,
                    'test_time' => $now,
                    'is_right' => $q->is_right,
                    'note_id' => !empty($q->note_ids) && isset($q->note_ids[0]) ? $q->note_ids[0] : $q->note_id,
                    'testarea_id' => $testarea_id,
                    'user_answer' => $q->answer,
                );

                $totalCount++;
                if ($q->is_right) $rightCount++;
            }

            if (!empty($values)) {
                global $uddb;
                $uddb->usertestsTable($this->userId)->batchInsert($values);
            }
        }
    }

    /**
     * 计算mini模考的分数
     * @param int $rightCount 做对题目数
     * @param int $duration 答题秒数
     * @return float
     */
    private function calMiniMokaoScore($rightCount, $duration) {
        $score = round($rightCount + (900 - $duration) / 1000, 4);
        return $score < 0.0 ? 0.0 : $score;
    }

    /**
     * 计算预估分
     * @param int $rightCount 做对题目数
     * @param int $totalCount 总题数
     * @param int $originalScore 试卷总分
     * @return int
     */
    private function calEntireScore($rightCount, $totalCount, $originalScore) {
        $n = rand(99, 101)/100;
        $rate = $rightCount/$totalCount;

        /* 2015-10-27 练习分数增加版本控制 */
        if ($_GET['terminal_type'] === 'android_phone' && version_cmp($_GET['app_version'], '1.2.6') < 0) {
            return $rate < 0.8 ? round($rate*$originalScore*$n) : round((1-$n+$rate*$n)*$originalScore);
        }

        return $rate < 0.8 ? round($rate*$originalScore*$n, 1) : round((1-$n+$rate*$n)*$originalScore, 1);
    }

    /**
     * 获取整卷的原始分数
     * @param int $paperId 试卷id
     * @param bool|false $isMock 是否为模考
     * @return array
     */
    private function getPaperScore($paperId, $isMock = false) {
        global $cmsdb;

        //模考则直接返回预设总分
        if ($isMock) {
            $score = getMockInfo($paperId)->score;
            return array('totalForCal' => $score);
        }

        //整卷情况
        $sql = "SELECT a.shortening FROM `original_ztk_papers` AS p INNER JOIN `original_ztk_area` AS a
        ON p.id ={$paperId} AND p.area_id = a.id";
        $areaCode = $cmsdb->getVar($sql);

        $sql = "SELECT `extra` FROM `data_exams` AS e INNER JOIN `data_exam_detail` AS ed ON e.exam_id=ed.exam_id
        AND e.shortening='{$areaCode}' LIMIT 1";
        $scoreInfo = $cmsdb->getVar($sql);

        if (empty($scoreInfo))
            return 100;

        $scoreInfo = unserialize($scoreInfo);
        $scores = explode(';', $scoreInfo['admission_score']);
        $xingceScore = explode(';', $scoreInfo['xingce_detail']);

        $totalForCal = empty($xingceScore[2]) ? 0 : $xingceScore[2];
        $total = empty($scores[0]) ? 0 : $scores[0];;
        $xingce = empty($scores[1]) ? 0 : $scores[1];
        $shenlun = empty($scores[2]) ? 0 : $scores[2];

        return compact('xingce', 'shenlun', 'total', 'totalForCal');
    }

    /**
     * 获取mini模考排名信息
     * @param int $paperId mini模考id
     * @param float $score 待比较分数
     * @return float
     */
    private function getMiniMokaoRank($paperId, $score) {
        global $cmsdb;

        $rankScore = intval($score);

        //获取模考日期
        $miniMockDate = $cmsdb->getVar('SELECT `publish_date` FROM `qb_paper_mokao` WHERE `id`=:id', array(':id' => $paperId));

        //通过缓存获取模考信息
        $miniMockInfo = getMiniMockInfo($miniMockDate);

        //更新模考答题统计
        if (isset($miniMockInfo['rank'][$rankScore])) {
            $miniMockInfo['rank'][$rankScore]++;
        } else {
            $miniMockInfo['rank'][$rankScore] = 1;
        }

        $miniMockInfo['persons_num']++;

        $cacheKey = 'func:getMiniMockInfo:'.$miniMockDate;
        cacheSet($cacheKey, $miniMockInfo, 24*60*60);

        //计算击败了多少
        $rank = $miniMockInfo['rank'];
        $total = $lower = 0;
        foreach ($rank as $score => $persons) {
            $total += $persons;
            if ($score < $rankScore) {
                $lower += $persons;
            }
        }

        return round($lower/$total, 2);
    }

    /**
     * 模考逻辑
     *
     * @param array $queryArgs 请求参数
     * @return array|bool
     */
    private function saveMock($queryArgs) {
        global $cmsdb;

        //获取试卷名称
        $paperTitle = getPaperName('mock', $queryArgs['paper_id']);

        if ($queryArgs['status'] == 'done' && $questions = json_decode($queryArgs['questions'])) {
            $rightCount = 0; //答对题目数
            $totalCount = 0; //答题总数
            $changedNotes = array(); //掌握级别改变的知识点

            $qIds = array();
            foreach ($questions as $q) {
                $qIds[] = $q->id;
            }
            $notes = getNotesByQuestions($qIds);

            foreach ($questions as $q) {
                //按知识点统计答题情况
                foreach ($notes[$q->id]['id'] as $noteId) {
                    if (!isset($changedNotes[$noteId]))
                        $changedNotes[$noteId] = array(
                            'total' => 0,
                            'right' => 0
                        );

                    $changedNotes[$noteId]['total']++;
                    if ($q->is_right) $changedNotes[$noteId]['right']++;
                }

                $totalCount++;
                if ($q->is_right) $rightCount++;
            }
            unset($questions);

            //计算本次模考导致的知识点掌握级别的变化
            $userData = getUserData($this->userId);
            $finalChangedNotes = calNoteLevel(normalizeUserNoteInfo($userData->note_info), $changedNotes, $userData->exam_detail_id); //能力产生变化的知识点

            //计算本次模考的预估分
            $originalScore = $this->getPaperScore($queryArgs['paper_id'], $queryArgs['paper_type'] === 'mock');
            $score = $this->calEntireScore($rightCount, $totalCount, $originalScore['totalForCal']);

            //是否已经提交过,防止重复提交
            $exists = $cmsdb->getVar('SELECT `id` FROM `qb_relationship_user_paper` WHERE `user_id`=:uid AND `paper_id`=:pid AND `paper_type`=8',
                array(':uid' => $queryArgs['user_id'], ':pid' => $queryArgs['paper_id']));

            if (empty($exists)) {
                //数据表插入练习记录
                $now = date('Y-m-d H:i:s');
                $cmsdb->insert('qb_relationship_user_paper', array(
                    'user_id' => $this->userId,
                    'paper_id' => $queryArgs['paper_id'],
                    'paper_type' => 8,
                    'paper_title' => $paperTitle,
                    'question_info' => $queryArgs['questions'],
                    'action_time' => $now,
                    'create_time' => $now,
                    'status' => $queryArgs['status'] == 'done' ? 1 : 2, //2为未完成
                    'duration' => $queryArgs['duration'],
                    'right_count' => $rightCount,
                    'total_count' => $totalCount,
                    'evaluate' => $score,
                    'note_info' => serialize($finalChangedNotes),
                    'terminal_type' => empty($_GET['terminal_type']) ? '' : trim($_GET['terminal_type'])
                ));

                $exerciseId = $cmsdb->lastInsertId();
            } else {
                $exerciseId = $exists;
            }

            return array(
                'exercise_name' => $paperTitle,
                'exercise_id' => intval($exerciseId),
                'notes' => $finalChangedNotes,
                'defeat' => 0.0,
                'score' => $score,
                'scores' => array()
            );
        }

        return false;
    }
}